#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>

#include "net.h"

char net_ipaccess_grant[IPACCESS_GRANT_LEN+1] = "";

int net_issocket(int fd)
{
	struct stat st;

	if(fstat(fd, &st) < 0)	return -1;
	if((st.st_mode & S_IFMT) == S_IFSOCK)
		return 1;

	return 0;
}

int net_iserror(int fd)
{
	int error = 0;
	socklen_t len = sizeof(int);

	if(fd < 0)	return -1;

	if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&error, &len) == -1){
		return -1;
	}

	return (error) ? 0 : -1;
}

int net_gethostbyteorder()
{
	union
	{
		short s;
		char c[sizeof(short)];
	} un;

	un.s = 0x0102;
	if(sizeof(short) == 2)
	{
		if(un.c[0] == 0x01 && un.c[1] == 0x02){
			return BIG_ENDIAN;
		}else if(un.c[0] == 0x02 && un.c[1] == 0x01){
			return LITTLE_ENDIAN;
		}else{
			return -1;
		}
	}

	return -1;
}

int net_getsockflag(int fd)
{
	int flag = -1;

	while((flag=fcntl(fd, F_GETFL, 0)) == -1)
	{
		if(errno == EINTR)	continue;
		return -1;
	}

	return flag;
}

void net_close_nodelay(int fd)
{
	struct linger linger;
	linger.l_onoff	= 1;
	linger.l_linger = 0;
	linger.l_onoff	= 1;
	linger.l_linger = 0;
	setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&linger, sizeof(linger));
	fcntl(fd, F_SETFL, 0);
	close(fd);
}

/****************************************************************
 * socket wrap function
 ****************************************************************/

int net_set_sa(struct sockaddr_in *sa, const char *ip, uint16_t port)
{
	bzero(sa, sizeof(struct sockaddr_in));
	sa->sin_family = AF_INET;
	if(ip == NULL){
		sa->sin_addr.s_addr = htonl(INADDR_ANY);
	}else{
		if((sa->sin_addr.s_addr=inet_addr(ip)) == INADDR_NONE)
			return -1;
	}
	sa->sin_port = htons(port);
	return 0;
}

int net_connect_ms(int sockfd, const struct sockaddr *s, socklen_t socklen, int msec, int isclose)
{
	if(msec <= 0){
		return net_connect_tv(sockfd, s, socklen, NULL, isclose);
	}else{
		struct timeval tv;
		tv.tv_sec = msec / 1000;
		tv.tv_usec = (msec % 1000) * 1000;
		return net_connect_tv(sockfd, s, socklen, &tv, isclose);
	}
	return 0;
}

int net_connect_tv(int sockfd, const struct sockaddr *s, socklen_t socklen, struct timeval *tv, int isclose)
{
	fd_set rfs, wfs;
	socklen_t len;
	int error = 0, flags;

	flags = fcntl(sockfd, F_GETFL, 0);
	fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);

	int n = connect(sockfd, s, (socklen_t)socklen);
	if(n < 0){
		if(errno != EINPROGRESS){
			if(isclose)	close(sockfd);
			return -1;
		}
	}else{
		fcntl(sockfd, F_SETFL, flags);
		return 0;
	}

	FD_ZERO(&rfs);
	FD_SET(sockfd, &rfs);

	FD_ZERO(&wfs);
	FD_SET(sockfd, &wfs);

	n = select(sockfd+1, &rfs, &wfs, NULL, tv);
	if(n == 0){
		close(sockfd);
		errno = ETIMEDOUT;
		return -1;
	}

	if(FD_ISSET(sockfd, &rfs) || FD_ISSET(sockfd, &wfs)){
		len = sizeof(error);
		if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0){
			if(isclose)	close(sockfd);
			return -1;
		}
	}

	fcntl(sockfd, F_SETFL, flags);
	if(error){
		if(isclose)	close(sockfd);
		errno = error;	return -1;
	}

	return 0;
}

int net_tcpconnect(const char *ip, uint16_t port, int sec)
{
	int sockfd = -1, ret;
	const int on = 1;
	struct sockaddr_in addr;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)	return -1;

	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	bzero(&addr, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(ip);
	addr.sin_port = htons(port);

	struct timeval tv = { .tv_sec = sec };
	ret = net_connect_tv(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_in), &tv, 1);
	if(ret == -1)	sockfd = -1;

	return sockfd;
}

int net_tcplisten(const char *ip, uint16_t port, int backlog)
{
	int lsfd = -1;
	const int on = 1;
	struct sockaddr_in addr;

	if((lsfd=socket(PF_INET, SOCK_STREAM, 0)) == -1){
		return -1;
	}

	setsockopt(lsfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	bzero(&addr, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	if(!ip)	
		addr.sin_addr.s_addr = htonl(INADDR_ANY);
	else	
		addr.sin_addr.s_addr = inet_addr(ip);
	addr.sin_port = htons(port);

	if(bind(lsfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1){
		close(lsfd);	return -1;
	}

	if(listen(lsfd, backlog) == -1){
		close(lsfd);	return -1;
	}

	return lsfd;
}

int net_udplisten(const char *ip, uint16_t port)
{
	int lsfd = -1;
	struct sockaddr_in addr;

	if((lsfd=socket(PF_INET, SOCK_DGRAM, 0)) == -1){
		return -1;
	}

	memset(&addr, '\0', sizeof(addr));
	addr.sin_family = AF_INET;
	if(!ip)
		addr.sin_addr.s_addr = htonl(INADDR_ANY);
	else
		addr.sin_addr.s_addr = inet_addr(ip);
	addr.sin_port = htons(port);

	if(bind(lsfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1){
		close(lsfd);	return -1;
	}

	return lsfd;
}

int net_accept(int lsfd, struct sockaddr *sa, socklen_t *addrlen)
{
	int sockfd = -1;
	socklen_t len = 0;

again:
	sockfd = accept(lsfd, sa, addrlen);
	if(sockfd < 0){
#ifdef EPROTO
		if(errno == EPROTO || errno == ECONNABORTED){
#else
		if(errno == ECONNABORTED){
#endif /*EPROTO*/
			goto again;
		}else{
			return -1;
		}
	}

	if(net_ipaccess_grant == NULL || net_ipaccess_grant[0] == '\0'){
		return sockfd;
	}

	struct sockaddr_in peeraddr;
	struct sockaddr_in localaddr;
	
	//char ip_net[INET_ADDRSTRLEN] = {0};

	len = sizeof(struct sockaddr_in);
	getpeername(sockfd, (struct sockaddr *)&peeraddr, &len);

	len = sizeof(struct sockaddr_in);
	getsockname(sockfd, (struct sockaddr *)&localaddr, &len);

	//TODO: check if the ip is illegal

	return sockfd;
}

int net_select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *timeo)
{
	int ret = 0;
again:
	ret = select(nfds, rfds, wfds, efds, timeo);
	if(ret < 0){
		if(errno == EINTR){
			if(timeo != NULL){
				//TODO: write log record
			}
			goto again;
		}
	}

	if(ret == 0)	errno = ETIMEDOUT;
	return ret;
}

/****************************************************************
 * set/get socket option
 ****************************************************************/

int net_set_rcvbuf(int fd, void *optval)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(int);
	return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, optval, (socklen_t)oplen);
}

int net_get_rcvbuf(int fd, void *optval)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(int);
	return getsockopt(fd, SOL_SOCKET, SO_RCVBUF, optval, (socklen_t *)&oplen);
}

int net_set_sndbuf(int fd, void *optval)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(int);
	return setsockopt(fd, SOL_SOCKET, SO_SNDBUF, optval, (socklen_t)oplen);
}

int net_get_sndbuf(int fd, void *optval)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(int);
	return getsockopt(fd, SOL_SOCKET, SO_SNDBUF, optval, (socklen_t *)&oplen);
}

int net_set_reuseaddr(int fd, int *optval)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(int);
	return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)optval, (socklen_t)oplen);
}

int net_get_reuseaddr(int fd, int *optval)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(int);
	return getsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)optval, (socklen_t *)&oplen);
}

int net_set_keepalive(int fd, int *optval)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(int);
	return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)optval, (socklen_t)oplen);
}

int net_get_keepalive(int fd, int *optval)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(int);
	return getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)optval, (socklen_t *)&oplen);
}

int net_set_rcvtimeo(int fd, struct timeval *tv)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(struct timeval);
	return setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, tv, (socklen_t)oplen);
}

int net_get_rcvtimeo(int fd, struct timeval *tv)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(struct timeval);
	return getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, tv, (socklen_t *)&oplen);
}

int net_set_sndtimeo(int fd, struct timeval *tv)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(struct timeval);
	return setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, tv, (socklen_t)oplen);
}

int net_get_sndtimeo(int fd, struct timeval *tv)
{
	if(fd <= 0)	return -1;

	int oplen = sizeof(struct timeval);
	return getsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, tv, (socklen_t *)&oplen);
}

int net_set_blocking(int fd)
{
	int val = 0, flags, mask;
	while((flags=fcntl(fd, F_GETFL, 0)) == -1) {
		if(errno == EINTR)	continue;
		return -1;
	}

	mask = flags;
	if(!(mask & O_NONBLOCK))	return 0;

	flags &= ~O_NONBLOCK;
	while((val=fcntl(fd, F_SETFL, flags)) == -1) {
		if(errno == EINTR)	continue;
		return -1;
	}
	return val;
}

int net_set_nonblocking(int fd)
{
	int val = 0;
	int flags, mask;
	while((flags=fcntl(fd, F_GETFL, 0)) == -1) {
		if(errno == EINTR)	continue;
		return -1;
	}

	mask = flags;
	if(mask & O_NONBLOCK)	return 0;

	flags |= O_NONBLOCK;
	while ((val=fcntl(fd, F_SETFL, flags)) == -1) {
		if (errno == EINTR)	continue;
		return -1;
	}
	return val;
}

/****************************************************************
 * read/write or recv/send function
 ****************************************************************/

int net_readable_ms(int fd, int msec)
{
	fd_set fds;
	struct timeval tv;

	tv.tv_sec = msec / 1000;
	tv.tv_usec = (msec % 1000) * 1000;

	FD_ZERO(&fds);
	FD_SET(fd, &fds);

	if(msec >= 0)
		return select(fd+1, &fds, NULL, NULL, &tv);
	else
		return select(fd+1, &fds, NULL, NULL, NULL);
}

int net_readable_tv(int fd, struct timeval *tv)
{
	fd_set fds;

	FD_ZERO(&fds);
	FD_SET(fd, &fds);

	return select(fd+1, &fds, NULL, NULL, tv);
}

int net_writeable_ms(int fd, int msec)
{
	fd_set fds;
	struct timeval tv;

	tv.tv_sec = msec / 1000;
	tv.tv_usec = (msec % 1000) * 1000;

	FD_ZERO(&fds);
	FD_SET(fd, &fds);

	if(msec >= 0)
		return select(fd+1, NULL, &fds, NULL, &tv);
	else
		return select(fd+1, NULL, &fds, NULL, NULL);
}

int net_writeable_tv(int fd, struct timeval *tv)
{
	fd_set fds;

	FD_ZERO(&fds);
	FD_SET(fd, &fds);

	return select(fd+1, NULL, &fds, NULL, tv);
}

ssize_t net_read_timeo(int fd, void *buf, size_t nbytes, int msec)
{
	ssize_t nread;
	size_t nleft = nbytes;
	char *ptr = (char *) buf;
	struct timeval tv, *tp;

	if(msec < 0){
		tp = NULL;
	}else{
		tv.tv_sec = msec / 1000;
		tv.tv_usec = (msec % 1000) * 1000;
		tp = &tv;
	}

	while(nleft > 0) 
	{
again:
		nread = net_read_tv(fd, ptr, nleft, tp);
		if(nread < 0){
			if((nread == -1) && (errno == EINTR)){
				goto again;
			}else if(nread == -2){
				errno = ETIMEDOUT;	return -1;
			}else if(nread == -3) {
				errno = EIO;	return -1;
			}else{
				return -1;
			}
		}else if(nread == 0){
			break;
		}else{
			ptr += nread;	nleft -= nread;
		}
	}
	return (ssize_t)(nbytes - nleft);
}

ssize_t net_write_timeo(int fd, void *buf, size_t nbytes, int msec)
{
	ssize_t nwrite;
	size_t nleft = nbytes;
	char *ptr = (char *)buf;
	struct timeval tv, *tp;

	if(msec < 0){
		tp = NULL;
	}else{
		tv.tv_sec = msec / 1000;
		tv.tv_usec = (msec % 1000) * 1000;
		tp = &tv;
	}

	int flag = net_getsockflag(fd);
	if(flag < 0)	return -1;

	if (!(flag & O_NONBLOCK)) 
		if(net_set_nonblocking(fd) < 0)	
			return -1;

	while(nleft > 0) 
	{
		nwrite = net_write_tv(fd, ptr, nleft, tp);
		if((nwrite == -1) && (errno == EINTR)){
			continue;
		}
		if(nwrite <= 0){
			if(nwrite == -2){
				errno = ETIMEDOUT;
			}
			if(!(flag & O_NONBLOCK)){
				net_set_blocking(fd);
			}
			return -1;
		}
		nleft -= nwrite;	ptr += nwrite;
	}
	if(!(flag & O_NONBLOCK))	net_set_blocking(fd);
	return (ssize_t)(nbytes - nleft);
}

ssize_t net_read_ms(int fd, void *buf, size_t nbytes, int msec)
{
	int err = 0;
	err = net_readable_ms(fd, msec);
	if(err < 0){
		return -2;
	}else if(err == 0){
		return -3;
	}
		
	return read(fd, buf, nbytes);
}

ssize_t net_read_tv(int fd, void *buf, size_t nbytes, struct timeval *tv)
{
	int err = 0;
	err = net_readable_tv(fd, tv);
	if(err < 0){
		return -2;
	}else if(err == 0){
		return -3;
	}
		
	return read(fd, buf, nbytes);
}

ssize_t net_recv_ms(int fd, void *buf, size_t nbytes, int msec)
{	
	int err = 0;
	err = net_readable_ms(fd, msec);
	if(err < 0){
		return -2;
	}else if(err == 0){
		return -3;
	}
	
	return recv(fd, buf, nbytes, 0);
}

ssize_t net_recv_tv(int fd, void *buf, size_t nbytes, struct timeval *tv)
{	
	int err = 0;
	err = net_readable_tv(fd, tv);
	if(err < 0){
		return -2;
	}else if(err == 0){
		return -3;
	}
	
	return recv(fd, buf, nbytes, 0);
}

ssize_t net_write_ms(int fd, void *buf, size_t nbytes, int msec)
{
	int err = 0;
	err = net_writeable_ms(fd, msec);
	if(err < 0){
		return -2;
	}else if(err == 0){
		return -3;
	}

	return write(fd, buf, nbytes);
}

ssize_t net_write_tv(int fd, void *buf, size_t nbytes, struct timeval *tv)
{
	int err = 0;
	err = net_writeable_tv(fd, tv);
	if(err < 0){
		return -2;
	}else if(err == 0){
		return -3;
	}

	return write(fd, buf, nbytes);
}

ssize_t net_send_ms(int fd, void *buf, size_t nbytes, int msec)
{
	int err = 0;
	err = net_writeable_ms(fd, msec);
	if(err < 0){
		return -2;
	}else if(err == 0){
		return -3;
	}

	return send(fd, buf, nbytes, 0);
}

ssize_t net_send_tv(int fd, void *buf, size_t nbytes, struct timeval *tv)
{
	int err = 0;
	err = net_writeable_tv(fd, tv);
	if(err < 0){
		return -2;
	}else if(err == 0){
		return -3;
	}

	return send(fd, buf, nbytes, 0);
}
